package com.rainyun.rc.orm;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.Date;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.rainyun.rc.orm.annotation.FieldInfo;
import com.rainyun.rc.orm.annotation.JsonField;
import com.rainyun.rc.orm.annotation.PrimaryField;
import com.rainyun.rc.orm.annotation.ReferField;
import com.rainyun.rc.common.VersionException;
import com.rainyun.rc.common.VersionUtil;
import com.rainyun.rc.util.RainStr;

public class MysqlSql extends CommonSql{

	public MysqlSql(Class<?> clazz, MysqlDB db) {
		super(clazz, db);
	}

	public static Map<Class<?>,String> OrmCreateTypes = new LinkedHashMap<Class<?>,String>();
	static {
		
		String intstr = "INT(10)";
		String dstr = "DOUBLE(10,2)";
		String longstr = "INT(20)";
		
		OrmCreateTypes.put(Byte.class,intstr);
		OrmCreateTypes.put(byte.class,intstr);
		OrmCreateTypes.put(byte[].class,"BLOB");
		OrmCreateTypes.put(Integer.class,intstr);
		OrmCreateTypes.put(int.class,intstr);
		OrmCreateTypes.put(Short.class,intstr);
		OrmCreateTypes.put(short.class,intstr);
		OrmCreateTypes.put(Long.class,longstr);
		OrmCreateTypes.put(long.class,longstr);
		
		OrmCreateTypes.put(Double.class,dstr);
		OrmCreateTypes.put(double.class,dstr);
		OrmCreateTypes.put(Float.class,dstr);
		OrmCreateTypes.put(float.class,dstr);
		
		OrmCreateTypes.put(Boolean.class,"BIT(1)");
		OrmCreateTypes.put(boolean.class,"BIT(1)");
		OrmCreateTypes.put(java.util.Date.class,"DATETIME");
		OrmCreateTypes.put(Date.class,"DATETIME");
		
		OrmCreateTypes.put(Enum.class,"CHAR(20)");
		OrmCreateTypes.put(String.class,"VARCHAR(255)");
	}

	/**数据库字段注解,用来设定一些额外内容*/
	@Documented
	@Target(ElementType.FIELD)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MysqlFieldInfo{
		/**数据库字段类型及其大小,如char(10) (无视大小写)<br>
		   * 默认会以字段类型本身决定<br>
		 * String >> varchar(255) <br>
		 * int >> int(10) <br>
		 * double >> double(10,2) <br>
		 * boolean >> tinyint(1)<br>
		 * */ 
		String type() default "";
		
		/**附加语句,对应原生mysql语句,如NOT NULL等*/
		String append() default "";
	}
	
	@Override
	public boolean init() throws VersionException,SQLException{
		MysqlDB db = (MysqlDB) cur_db;
		Connection con =  db.connection;
		if(con==null) {
			if(!db.connect()) {
				return false;
			}
			con =  db.connection;
		}
		List<Map<String, Object>> re = null;
		try {
			re = cur_db.sqlQuery("SELECT COLUMN_NAME,COLUMN_TYPE " + 
					"FROM information_schema.`COLUMNS` " + 
					"WHERE TABLE_SCHEMA = '"+db.getDatabase()+"' and TABLE_NAME = '"+getPath()+"'");
			
		} catch (SQLException e1) {
			cur_db.handleError(getPath(),e1);
			throw e1;
		}
		
		Map<String,String> db_types = new HashMap<String, String>();//目前数据库内的字段
		for(Map<String, Object> o:re) {
			db_types.put(o.get("COLUMN_NAME").toString(), o.get("COLUMN_TYPE").toString());
		}
		Map<String,String> types = new HashMap<String, String>();//目前实体字段
		List<String> field_sql = new ArrayList<String>();
		List<String> othersql = new ArrayList<String>();
		
		for (Entry<String, Field> en:cur_info.getFields().entrySet()) {
			String name = en.getKey();
			Field f = en.getValue();
			
			StringBuffer tsql = new StringBuffer();
			String type = OrmCreateTypes.getOrDefault(f.getType(),"varchar(255)");
			if(f.isAnnotationPresent(JsonField.class)) {
				type = "varchar(1024)";
			}
			MysqlFieldInfo af = f.getAnnotation(MysqlFieldInfo.class);
			String append = "";
			if(af!=null) { //字段额外属性
				if(!af.type().isEmpty()) {
					type = af.type();
				}
				append = af.append();
			}
			types.put(name,type);
			tsql.append(name+" "+type+" "+append);
			
			FieldInfo af2 = f.getAnnotation(FieldInfo.class);
			if(af2!=null) {
				if(af2.auto_increase())
					tsql.append(" auto_increment");
			}
			if(f.isAnnotationPresent(PrimaryField.class))
				othersql.add("PRIMARY KEY ( "+name+" )");
			ReferField ref = f.getAnnotation(ReferField.class);
			if(ref!=null&&!ref.fake()) {
				String reff = ref.field().equals("")?
						cur_db.getClazzInfo(ref.to()).getPrimary()
						:ref.field();
				String tmp = "CONSTRAINT fk_"+getPath()+"_"+name+
						" FOREIGN KEY ("+name+") REFERENCES "+
						cur_db.getClazzInfo(ref.to()).getPath()+"("+reff+") ";
				tmp+="ON DELETE "+ref.onDelete().toString().replace("_"," ")+" ";
				tmp+="ON UPDATE "+ref.onUpdate().toString().replace("_"," ")+" ";
				othersql.add(tmp);
			}
			field_sql.add(tsql.toString());
		}
		String sql = "CREATE TABLE IF NOT EXISTS "+getPath()+" (";
		sql+=RainStr.join(field_sql.toArray(new String[0]), ",");
		if(othersql.size()>0) {
			sql+=","+RainStr.join(othersql.toArray(new String[0]), ",");
		}
		sql+=")";
		String curver = VersionUtil.getVersion(cur_clazz);
		if(curver!=null){
			sql+=" COMMENT '"+getPath()+">>>"+curver+"'";
		}

		try {
			cur_db.begin();
			if (db_types.isEmpty()){
				cur_db.sqlUpdate(sql);
				cur_db.setDataVersion(cur_clazz);//更新表版本
			}else{
				String filever = cur_db.getDataVersion(cur_clazz);
				int comp = VersionUtil.compareToVer(curver,filever) ;
				comp = Math.abs(comp);
				if(comp!=1) {
					if(comp==2) {
						for(Entry<String, String> en:types.entrySet()) {//字段覆盖
							String db_type = db_types.get(en.getKey());
							if(db_type!=null) {
								if(!db_type.equals(en.getValue())) {//类似不匹配>>强改
									cur_db.sqlUpdate("ALTER TABLE "+getPath()+" MODIFY COLUMN "+en.getKey()+" "+en.getValue());
								}
							}else {//无字段>>强加
								cur_db.sqlUpdate("ALTER TABLE "+getPath()+" ADD COLUMN "+en.getKey()+" "+en.getValue());
							}
						}
						cur_db.setDataVersion(cur_clazz);//更新表版本
					}
				}else {//不兼容
					throw new VersionException(VersionUtil.getVersion(cur_clazz),filever);
				}
			}
			cur_db.commit();
		} catch (SQLException e) {
			cur_db.rollback();
			cur_db.handleError(getPath(),e);
			throw e;
		}
		return true;
	}

}




